/*
 * Project CHEN Rendering Engine : /texture.hpp
 * Created: 2021-03-01, Last modified:  2021-03-01
 * 
 * This is the texture core file header.
 * 
 * Copyright (C) 2021 Hineven (hineven@pku.edu.cn)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifndef _H_TEXTURE
#define _H_TEXTURE
#include "heads.hpp"
#include "interaction.hpp"
namespace chen {
template<class T> class Texture {
public:
    // This function should be responsible for antialiasing
    // Reduce the original texture function to appropirate 
    // frequency field for sampling.
    virtual T sample (const SurfInteract & it) const = 0;
};
class UVMapping {
    public:
    // This function should map positions from the surface uv
    // coordinates to the st texture coordinates. Also returns
    // the relating derivatives,
    virtual Point2 map (const Point2 & uv, Point2 * dsduv, Point2 * dtduv) const = 0;
};

// The most simple texture.
template<class T> class ConstantTexture: public Texture<T> {
public:
    T C;
    inline ConstantTexture (const T & c): C(c) {}
    inline T sample (const SurfInteract & si) const {
        unused(si);
        return C;
    }
};

#define INT(a) int(floor(a))

// Chessboard displays X when floor(x)+floor(y) is even
// otherwise it displays Y
template<class T> class ChessboardTexture: public Texture<T> {
public:
    const UVMapping * mapping;
    const Texture<T> * X, * Y;
    inline ChessboardTexture (const UVMapping * mapping, const Texture<T> * x, const Texture<T> * y):
        mapping(mapping), X(x), Y(y) {}
    inline T sample (const SurfInteract & si) const {
        Point2 dsduv, dtduv, st;
        st = mapping->map(si.uv, &dsduv, &dtduv);
        // Calculate differentials
        /*
        Float dsdx = dsduv.x*si.dudx+dsduv.y*si.dvdx;
        Float dsdy = dsduv.x*si.dudy+dsduv.y*si.dvdy;
        Float dtdx = dtduv.x*si.dudx+dtduv.y*si.dvdx;
        Float dtdy = dtduv.x*si.dudy+dtduv.y*si.dvdy;
        // Filter out higher frequencies
        Float ds = max(abs(dsdx), abs(dsdy)), dt = max(abs(dtdx), abs(dtdy));
        // Calculate box region integral
        Float s0 = st.x-ds, s1 = st.x+ds;
        Float t0 = st.y-dt, t1 = st.y+dt;
        if(INT(s0) == INT(s1) && INT(t0) == INT(t1))
            return ((INT(s0)+INT(t0)) & 1) ? Y->sample(si) : X->sample(si);
        std::function<Float(int, Float)> calc = [] (Float l, Float r) {
            int li = INT(l), ri = INT(r);
            return ((ri+1)/2 - (li+1)/2) + (((ri&1) ? 0 : r-ri) - ((li&1) ? 0 : l-li));
        };
        Float Xfrac;
        int sl = INT(s0), tl = INT(t0);
        if(INT(s0) == INT(s1)) {
            Xfrac = calc(sl+t0, sl+t1)*(s1-s0);
        } else if(INT(t0) == INT(t1)) {
            Xfrac = calc(tl+s0, tl+s1)*(t1-t0);
        } else {
            int su = ceil(s1), tu = ceil(t1);
            Float w_nx = (su+tl+1)/2-(sl+tl+1)/2;
            Float s_nx = w_nx*(tu-tl) + (su-sl-2*w_nx)*((tu-tl)/2);
            Xfrac = s_nx - 
                        (
                        (((tl+sl)&1) ? 0 : (t0-tl)*(s0-sl))
                        + (((tl+su)&1) ? (su-s1)*(t0-tl) : 0)
                        ) - (
                        (((tu+sl)&1) ? (tu-t1)*(s0-sl) : 0)
                        + (((tu+su)&1) ? 0 : (su-s1)*(tu-t1))
                        ) - (
                        calc(tl+s0, tl+s1)*(t0-tl)
                        + calc(tu-1+s0, tu-1+s1)*(tu-t1)
                        ) - (
                        calc(sl+t0, sl+t1)*(s0-sl)
                        + calc(su-1+t0, su-1+t1)*(su-s1)
                        );
        }
        Xfrac /= (s1-s0)*(t1-t0);
        return Xfrac*(X->sample(si)) + (1-Xfrac)*(Y->sample(si));*/
        return ((INT(st.x)+INT(st.y))&1) ? Y->sample(si) : X->sample(si);
    }
};

#undef INT

template<class T> class BilinearTexture: public Texture<T> {
public:
    const UVMapping * mapping;
    const Texture<T> * BL, * BR, * TL, * TR;
    inline BilinearTexture (const UVMapping * mapping,
        const Texture<T> * BL, const Texture<T> * BR, const Texture<T> * TL, const Texture<T> * TR):
            mapping(mapping), BL(BL), BR(BR), TL(TL), TR(TR) {}
    // This texture doesnt support antialiasing
    inline T sample (const SurfInteract & si) const {
        Point2 dsduv, dtduv;
        Point2 st = mapping->map(si.uv, &dsduv, &dtduv);
        return (1-st.x) * (1-st.y)*BL + (1-st.x)*(st.y)*TL +
            (st.x)*(1-st.y)*BR + (st.x)*(st.y)*TR;
    }
};

// Simply maps the UV without rotations or nonlinear distortions.
// st = uv*scale + shift
class DirectUVMapping: public UVMapping {
    Float xscale, yscale;
    Float xshift, yshift;
public:
    inline DirectUVMapping (
        const Float & xscale, const Float & yscale,
        const Float & xshift, const Float & yshift):
        xscale(xscale), yscale(yscale),
        xshift(xshift), yshift(yshift) {}
    inline Point2 map (const Point2 & uv, Point2 * dsduv, Point2 * dtduv) const {
        dsduv->x = xscale;
        dsduv->y = 0;
        dtduv->x = 0;
        dtduv->y = yscale;
        return Point2(uv.x*xscale+xshift, uv.y*yscale+yshift);
    }
};
}
#endif